package zust.study.blockchain.demo;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * @author senselyan
 * @Description: ${todo}
 * @date 2018/3/19 16:02
 */
public class Blockchain {

    /** Block 链 */
    private List<Block> blockchain = new LinkedList<>();

    /** 待生成Block的交易 */
    private Set<Transaction> transactions = new HashSet<>();


    /**
     * 初始化的时候，创建genesis block
     * @throws NoSuchAlgorithmException
     */
    public Blockchain() throws NoSuchAlgorithmException {
        //genesis block，真实场景下，应该是同步区块链的最长链最后一个区块
        newBlock(720L);
    }

    /**
     * 挖矿产生新的区块
     * @throws NoSuchAlgorithmException
     */
    public void mine() throws NoSuchAlgorithmException {
        Block lastBlock = blockchain.get(blockchain.size() - 1);
        Long lastProof = lastBlock.getProof();
        Long proof = proofOfWork(lastProof);
        newTransaction("0", "mineNode", 1L);
        newBlock(proof);

    }

    /**
     * 计算工作证明
     * @param lastProof
     * @return
     * @throws NoSuchAlgorithmException
     */
    public Long proofOfWork(Long lastProof) throws NoSuchAlgorithmException {
        Long proof = 0L;

        while (!validProof(lastProof, proof)) {
            proof++;
        }
        return proof;
    }

    /**
     * 算题，这里定义为，直到算出来的值以0000开头，才算正确答案
     * @param lastProof
     * @param proof
     * @return
     * @throws NoSuchAlgorithmException
     */
    private boolean validProof(Long lastProof, Long proof) throws NoSuchAlgorithmException {
        String guess = "" + lastProof + proof;
        String guessHash = hash(guess);
        return guessHash.startsWith("0000");
    }

    /**
     * 新建一个区块
     * @param proof
     * @return
     * @throws NoSuchAlgorithmException
     */
    public Block newBlock(Long proof) throws NoSuchAlgorithmException {
        Set<Transaction> transactions = new HashSet<>();
        transactions.addAll(this.transactions);
        int index = blockchain.size() - 1;
        Block lastBlock = null;
        if (index >= 0) {
            lastBlock = blockchain.get(index);
        }
        if (lastBlock == null) {
            lastBlock = new Block();
        }
        Block block = new Block();
        block.setIndex(Long.valueOf(blockchain.size() + 1));
        block.setTimestamp(System.currentTimeMillis());
        block.setTransactions(transactions);
        block.setProof(proof);
        block.setPreviousHash(hash(lastBlock.toString()));
        blockchain.add(block);
        this.transactions.clear();
        return block;
    }

    /**
     * 提交一笔新的交易
     * @param sender
     * @param recipient
     * @param amount
     * @return
     */
    public long newTransaction(String sender, String recipient, Long amount) {
        Transaction transaction = new Transaction();
        transaction.setSender(sender);
        transaction.setRecipient(recipient);
        transaction.setAmount(amount);
        this.transactions.add(transaction);

        int lastBlockIndex = blockchain.size() - 1;
        if (lastBlockIndex >= 0) {
            Block block = blockchain.get(lastBlockIndex);
            return block.getIndex() + 1;
        }
        return 0;
    }

    /**
     * SHA-512 算工作量证明
     * @param value
     * @return
     * @throws NoSuchAlgorithmException
     */
    private static String hash(String value) throws NoSuchAlgorithmException {
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
        messageDigest.update(value.getBytes());
        return bytesToHexString(messageDigest.digest());
    }

    private static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    public List<Block> getBlockchain() {
        return blockchain;
    }

    public Set<Transaction> getTransactions() {
        return transactions;
    }

}
